using System;
using System.Web;
using System.Collections.Generic;
using System.Data;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using System.Reflection;
using System.IO;
using System.Net;
using System.Collections;
using System.Text.RegularExpressions;
using System.Xml;
using Microsoft.SharePoint.Navigation;
using System.Xml.Serialization;
using System.Threading;

namespace SharePointer.Utilities
{
    public class TimerJobScheduler : SPJobDefinition
    {
        SPSite Site;
        SPWeb Web;

        public TimerJobScheduler() : base() { ;}

        public TimerJobScheduler(string psJobName, SPService service, SPServer server, SPJobLockType targetType) : base(psJobName, service, server, targetType) { ;}

        public TimerJobScheduler(string psJobName, SPWebApplication webApplication)
            : base(psJobName, webApplication, null, SPJobLockType.ContentDatabase)
        {
            this.Title = "TimerJobScheduler";
        }

        public override void Execute(Guid contentDbId)
        {
            Site = null;
            Web = null;
            try
            {
                // Add Logging here - Log.Write("Timer Jobs Executing. Searching for Schedules from TimerJobSchedule");
                SPWebApplication webApplication = (SPWebApplication)this.Parent;
                Site = webApplication.Sites[0];
                Web = Site.RootWeb;

                //Run The jobs
                RunScheduleJobs();
            }
            catch (Exception ex)
            {
                // Add Logging here - Log.Write("Fatal Error - " + ex.Message.ToString());
            }
            finally
            {
                if (Site != null)
                {
                    Site.Dispose();
                }
                if (Web != null)
                {
                    Web.Dispose();
                }
            }
        }

        public void RunScheduleJobs()
        {

            bool RunDayTrue;
            bool RunTimeTrue;
            DateTime NowDateTime = DateTime.Now;
            string TodaysDay = DateTime.Now.DayOfWeek.ToString();
            string HourNow = DateTime.Now.Hour.ToString();
            string NowDateTimeShortString = NowDateTime.ToString("yyyyMMddHH");

            SPList List = Web.Lists["TimerJobSchedule"];
            SPListItemCollection Items = List.Items;

            foreach (SPListItem Item in Items)
            {
                RunDayTrue = false;
                RunTimeTrue = false;

                if (Item["Status"].ToString().ToLower() == "enabled")
                {
                    try
                    {
                        bool AutoRun = bool.Parse(Item["RunAtNextOpportunity"].ToString());

                        if (!AutoRun)
                        {

                            if (LastRunDateTimeShortString(Item) != NowDateTimeShortString)
                            {
                                //DayOfWeek
                                string daysToRun = Item["DaysToRun"].ToString().Replace(";#", "#");
                                string[] DaysToRunArray = daysToRun.Split(new char[] { '#' });

                                foreach (string SelectedDay in DaysToRunArray)
                                {
                                    if (SelectedDay != "" && SelectedDay == TodaysDay)
                                    {
                                        RunDayTrue = true;
                                        break;
                                    }
                                }

                                //TimeofTheDay
                                string hoursToRunArray = Item["HoursToRun"].ToString().Replace(";#", "#");
                                string[] HoursToRunArray = hoursToRunArray.Split(new char[] { '#' });


                                foreach (string SelectedHour in HoursToRunArray)
                                {
                                    if (SelectedHour != "")
                                    {
                                        string Hour24ClockTime = Get24HourString(SelectedHour);
                                        if (Hour24ClockTime == HourNow)
                                        {
                                            RunTimeTrue = true;
                                            break;
                                        }
                                    }
                                }
                            }
                        }


                        //Run The job if RunDayTrue and RunTimeTrue or if AutoRun is true
                        if ((RunDayTrue && RunTimeTrue) || AutoRun)
                        {
                            try
                            {
                                // Add Logging here - Log.Write("Timer Job Instance Start - " + Item["Title"].ToString() + ".");
                                //Run the Job
                                RunTheTimerJobInstance(Item);

                            }
                            catch (Exception ex)
                            {
                                //Log Exception
                                // Add Logging here - Log.Write("The TimerJob Instance - " + Item["Title"].ToString() + " from the TimerJobSchedule list failed to run. Exception - " + ex.Message.ToString());
                            }
                            finally
                            {
                                //Set the DateTimeStamp and potentially reset Autorun
                                Item.Web.AllowUnsafeUpdates = true;
                                Item["LastRunDateTime"] = NowDateTime.ToString();
                                Item["RunAtNextOpportunity"] = 0;

                                Item.SystemUpdate();

                            }


                        }
                    }
                    catch (Exception ex)
                    {
                        // Add Logging here - Log.Write("TimerSceduling Error. Exception - " + ex.Message.ToString());
                    }
                }
                else
                {
                    //Log the fact the job is disabled
                }
            }

            // Add Logging here - Log.Write("Timer Jobs Finished Searching for jobs.");
        }

        private void RunTheTimerJobInstance(SPListItem Item)
        {
            //Get The Assembly
            Assembly ThisJobsAssembly = GetAssembly(Item["Assembly"].ToString());
            if (ThisJobsAssembly == null)
            {
                // Add Logging here - Log.Write("TimerJobInstance has Failed to execute. Assembly - " + Item["Assembly"].ToString() + " for TimerJobInstance - " + Item["Title"].ToString() + " Could Not be loaded.");
                throw new Exception("TimerJobInstance has Failed at GetAssembly");
            }

            //Get The Type for The Class
            Type ThisJobsClassType = GetClass(ThisJobsAssembly, Item["Class"].ToString());
            if (ThisJobsClassType == null)
            {
                // Add Logging here - Log.Write("TimerJobInstance has Failed to execute. Class - " + Item["Class"].ToString() + " for TimerJobInstance - " + Item["Title"].ToString() + " Could Not be loaded.");
                throw new Exception("TimerJobInstance has Failed at GetClass");
            }

            //Constructor Parameters
            Object[] parameters = new Object[] { };
            if (Item["ConstructorParameter1"] != null && Item["ConstructorParameter1"].ToString() != "")
            {
                try
                {
                    parameters = BuildConstructorParams(Item);
                }
                catch
                {
                    parameters = new Object[] { };
                    //Log the Error
                }
            }

            //Initiate the Class with constructor parameters
            Object ThisJobsClassInstance = GetClassInstance(ThisJobsClassType, "", BindingFlags.CreateInstance, null, null, parameters);
            if (ThisJobsClassInstance == null)
            {
                // Add Logging here - Log.Write("TimerJobInstance has Failed to execute. ClassInstance of class - " + Item["Class"].ToString() + " for TimerJobInstance - " + Item["Title"].ToString() + " Could Not be loaded. Please check the constructor parameters.");
                throw new Exception("TimerJobInstance has Failed at GetClassInstance");
            }

            //Get the Method
            MethodInfo ThisJobsMethod = GetMethod(ThisJobsClassType, Item["Method"].ToString());
            if (ThisJobsMethod == null)
            {
                // Add Logging here - Log.Write("TimerJobInstance has Failed to execute. Method - " + Item["Method"].ToString() + " for TimerJobInstance - " + Item["Title"].ToString() + " Could Not be loaded.");
                throw new Exception("TimerJobInstance has Failed at GetMethod"); ;
            }

            //Execute a Method on a thread
            InvokingMethod ExecuteThisMethod = new InvokingMethod(ThisJobsMethod, ThisJobsClassInstance);
            Thread ThisThread = new Thread(ExecuteThisMethod.Invoke);
            ThisThread.Name = Item["Title"].ToString();
            ThisThread.Start();

            // Add Logging here - Log.Write("TimerJobInstance - " + Item["Title"].ToString() + " Executing!");
        }

        private Assembly GetAssembly(string AssemblyName)
        {
            try
            {
                return Assembly.Load(AssemblyName);
            }
            catch (Exception ex)
            {
                // Add Logging here - Log.Write("Assembly load failed. Exception - " + ex.Message.ToString());
                return null;
            }

        }

        private Type GetClass(Assembly ThisJobsAssembly, string ClassName)
        {
            Type ClassType;
            try
            {
                ClassType = ThisJobsAssembly.GetType(ClassName);
                return ClassType;
            }
            catch (Exception ex)
            {
                // Add Logging here - Log.Write("Class load failed. Exception - " + ex.Message.ToString());
                return null;
            }
        }

        private object GetClassInstance(Type ThisJobsClassType, string p, BindingFlags bindingFlags, Binder binder, object p_4, object[] parameters)
        {

            try
            {
                return ThisJobsClassType.InvokeMember(p, bindingFlags, binder, p_4, parameters);
            }
            catch (Exception ex)
            {
                // Add Logging here - Log.Write("Class Instance could not be created. Exception - " + ex.Message.ToString());
                return null;
            }
        }

        private MethodInfo GetMethod(Type ThisJobsClassType, string p)
        {
            try
            {
                return ThisJobsClassType.GetMethod(p);
            }
            catch (Exception ex)
            {
                // Add Logging here - Log.Write("Method load failed. Exception - " + ex.Message.ToString());
                return null;
            }
        }

        private Object[] BuildConstructorParams(SPListItem Item)
        {
            Object[] ThisParamsObject;
            string Param1;
            string Param2;
            string Param3;
            string Param4;

            if (Item["ConstructorParameter1"] != null && Item["ConstructorParameter1"].ToString() != "")
            {
                Param1 = Item["ConstructorParameter1"].ToString();
            }
            else
            {
                ThisParamsObject = new Object[] { };
                return ThisParamsObject;
            }

            if (Item["ConstructorParameter2"] != null && Item["ConstructorParameter2"].ToString() != "")
            {
                Param2 = Item["ConstructorParameter2"].ToString();
            }
            else
            {
                ThisParamsObject = new Object[] { Param1 };
                return ThisParamsObject;
            }

            if (Item["ConstructorParameter3"] != null && Item["ConstructorParameter3"].ToString() != "")
            {
                Param3 = Item["ConstructorParameter3"].ToString();
            }
            else
            {
                ThisParamsObject = new Object[] { Param1, Param2 };
                return ThisParamsObject;
            }


            if (Item["ConstructorParameter4"] != null && Item["ConstructorParameter4"].ToString() != "")
            {
                Param4 = Item["ConstructorParameter4"].ToString();
            }
            else
            {
                ThisParamsObject = new Object[] { Param1, Param2, Param3 };
                return ThisParamsObject;
            }


            return ThisParamsObject = new Object[] { Param1, Param2, Param3, Param4 };
        }

        private string Get24HourString(string SelectedHour)
        {
            switch (SelectedHour)
            {
                case "12am":
                    return "0";
                case "1am":
                    return "1";
                case "2am":
                    return "2";
                case "3am":
                    return "3";
                case "4am":
                    return "4";
                case "5am":
                    return "5";
                case "6am":
                    return "6";
                case "7am":
                    return "7";
                case "8am":
                    return "8";
                case "9am":
                    return "9";
                case "10am":
                    return "10";
                case "11am":
                    return "11";
                case "12pm":
                    return "12";
                case "1pm":
                    return "13";
                case "2pm":
                    return "14";
                case "3pm":
                    return "15";
                case "4pm":
                    return "16";
                case "5pm":
                    return "17";
                case "6pm":
                    return "18";
                case "7pm":
                    return "19";
                case "8pm":
                    return "20";
                case "9pm":
                    return "21";
                case "10pm":
                    return "22";
                case "11pm":
                    return "23";

            }
            return "";
        }

        public string LastRunDateTimeShortString(SPListItem Item)
        {
            if (Item["LastRunDateTime"] != null)
            {
                DateTime LastRunDateTime = DateTime.Parse(Item["LastRunDateTime"].ToString());
                return LastRunDateTime.ToString("yyyyMMddHH");
            }

            return "";
        }
    }

    public class InvokingMethod
    {
        MethodInfo InvokeMethod;
        Object InvokeClass;

        public InvokingMethod(MethodInfo ThisJobsMethod, Object ThisJobsClass)
        {
            InvokeMethod = ThisJobsMethod;
            InvokeClass = ThisJobsClass;
        }

        public void Invoke()
        {
            InvokeMethod.Invoke(InvokeClass, null);
        }
    }
}
